#include "parser.h"
#include "predicate.h"

enum SYMLINK_OPTION parse_sym_option(char *argv[], int *arg_idx, int argc)
{
    /* set default to NO_FOLLOW */
    enum SYMLINK_OPTION symlink_option = NO_FOLLOW;
    while(*arg_idx < argc) {
        /* legal values: -P, -L, -H */
        if((strlen(argv[*arg_idx]) != 2) || ('-' != argv[*arg_idx][0]))
            break;
        if('P' == argv[*arg_idx][1]) {
            symlink_option = NO_FOLLOW;
            *arg_idx += 1;
        } else if('L' == argv[*arg_idx][1]) {
            symlink_option = FOLLOW;
            *arg_idx += 1;
        } else if('H' == argv[*arg_idx][1]) {
            symlink_option = ONLY_LEVEL0;
            *arg_idx += 1;
        } else {
            break;
        }
    }

    return symlink_option;
}

/* pre declaration as there is cyclic reference */
predicate *next_pred(char *argv[], int *arg_idx, int argc);

/**
 * Recursive-style test argument parsing.
 * Pre-condition:
 *   there are parsable tests with closing parenthesis )
 * Post-conditin:
 *   *arg_idx is at next parsable position
 */
predicate *parse_tests_inner(char *argv[], int *arg_idx, int argc)
{
    predicate *pred = NULL;

    /* get first predicate */
    pred = next_pred(argv, arg_idx, argc);

    /* normal iteration */
    while(*arg_idx < argc) {
        if(')' == argv[*arg_idx][0]) {
            *arg_idx += 1;
            return pred;
        }

        if(strcmp("-a", argv[*arg_idx]) == 0) {
            *arg_idx += 1;
            pred = and_predicate(pred, next_pred(argv, arg_idx, argc));
        } else if(strcmp("-o", argv[*arg_idx]) == 0) {
            *arg_idx += 1;
            pred = or_predicate(pred, next_pred(argv, arg_idx, argc));
        } else {
            pred = and_predicate(pred, next_pred(argv, arg_idx, argc));
        }
    }

    /* reaching this point means no ) is encountered */
    error_exit(1, "unmatched '('", NULL);

    /* impossible to reach here */
    return pred;
}

/**
 * Get next predicate.
 * Pre-condition
 *   there is at least one test to parse
 * Post-condition:
 *   *arg_idx is at next parsable index
 */
predicate *next_pred(char *argv[], int *arg_idx, int argc)
{
    if(*arg_idx == argc) {
        error_exit(1, "more test is needed!\n", NULL);
    }

    predicate *pred = NULL;
    /* try parse tests */
    if(strcmp("-a", argv[*arg_idx]) == 0 ||
            strcmp("-o", argv[*arg_idx]) == 0) {
        error_exit(1, "-a or -o test must have left-hand operand!", NULL);
    } else if('(' == argv[*arg_idx][0]) {
        *arg_idx += 1;
        pred = parse_tests_inner(argv, arg_idx, argc);
    } else if('!' == argv[*arg_idx][0]) {
        *arg_idx += 1;
        pred = not_predicate(next_pred(argv, arg_idx, argc));
    } else if(strcmp("-name", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -name", NULL);
        }
        pred = name_predicate(argv[*arg_idx]);
        *arg_idx += 1;
    } else if(strcmp("-type", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -type", NULL);
        }
        switch(argv[*arg_idx][0]) {
            case 'b': pred = type_predicate(DT_BLK); break;
            case 'c': pred = type_predicate(DT_CHR); break;
            case 'd': pred = type_predicate(DT_DIR); break;
            case 'p': pred = type_predicate(DT_FIFO); break;
            case 'f': pred = type_predicate(DT_REG); break;
            case 'l': pred = type_predicate(DT_LNK); break;
            case 's': pred = type_predicate(DT_SOCK); break;
            default: error_exit(1, "unrecognized file type %s", argv[*arg_idx]);
        }
        *arg_idx += 1;
    } else if(strcmp("-uid", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -uid", NULL);
        }
        pred = uid_predicate(atoi(argv[*arg_idx]));
        *arg_idx += 1;
    } else if(strcmp("-user", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -user", NULL);
        }
        struct passwd *pwst = getpwnam(argv[*arg_idx]);
        if(pwst) {
            pred = uid_predicate(pwst->pw_uid);
        } else {
            error_exit(1, "user %s does not exist", argv[*arg_idx]);
        }
        *arg_idx += 1;
    } else if(strcmp("-atime", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -atime", NULL);
        }
        int days = atoi(argv[*arg_idx]);
        pred = amctime_predicate(days, ACCESS);
        *arg_idx += 1;
    } else if(strcmp("-mtime", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -mtime", NULL);
        }
        int days = atoi(argv[*arg_idx]);
        pred = amctime_predicate(days, MODIFY);
        *arg_idx += 1;
    } else if(strcmp("-ctime", argv[*arg_idx]) == 0) {
        *arg_idx += 1;
        if(*arg_idx == argc) {
            error_exit(1, "incomplete test: -ctime", NULL);
        }
        int days = atoi(argv[*arg_idx]);
        pred = amctime_predicate(days, CHANGE);
        *arg_idx += 1;
    } else {
        error_exit(1, "unrecognized test %s!", argv[*arg_idx]);
    }

    return pred;
}

/**
 * Parse tests arguments.
 * parentheses should separated from tests!
 */
predicate *parse_tests(char *argv[], int *arg_idx, int argc)
{
    /* default to true*/
    predicate *pred = true_predicate();

    /* normal iteration */
    while(*arg_idx < argc) {
        if(is_action(argv[*arg_idx]))
            break;

        if(strcmp("-a", argv[*arg_idx]) == 0) {
            *arg_idx += 1;
            pred = and_predicate(pred, next_pred(argv, arg_idx, argc));
        } else if(strcmp("-o", argv[*arg_idx]) == 0) {
            *arg_idx += 1;
            pred = or_predicate(pred, next_pred(argv, arg_idx, argc));
        } else {
            pred = and_predicate(pred, next_pred(argv, arg_idx, argc));
        }
    }

    return pred;
}

